/**
 * 1) Читаем карту
 * 2) Выводим карту.
 * 3) Засекаем время.
 * 4) Ждём.
 * 5) Читаем. Если на вход ничего не поступило, возвращаем код ошибки. Иначе считываем вывод.
 * 6) Обрабаывает вывод. Выводим результат.
 * 
 * 
 */
#include <iostream>
#include <string>
#include <sstream>
#include <fstream>
#include <exception>
#include <stdexcept>
#include <cstdlib>
#include <cstdio>

#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h> 


#include "game_errors.h"
#include "config.h"
#include "bugmap.h"
 
const std::string PATH_CONFIG("games.cfg");

//#define DEBUG 1
//#define LOG 1

#define MAX_NUMBER_STEP 1000 

#define VAL_SEC(x) x.it_value.tv_sec
#define VAL_MSEC(x) x.it_value.tv_usec
#define INT_SEC(x) x.it_interval.tv_sec
#define INT_MSEC(x) x.it_interval.tv_usec


using namespace std; 


//Потоки для работы с каналами и лог файлом
ofstream player1_stdin;
ifstream player1_stdout;
ofstream player2_stdin;
ifstream player2_stdout;
ofstream game_log("log.txt");
ofstream game_result("result.txt");

//Код, возвращаемый чекером. Смотри game_errors.h
u_int EXIT_CODE;

void checkPipes(Config& config){
	struct stat buf;
	if (!access(config.getPathToPlayer1STDIN().c_str(), F_OK)){
		//Если файл существует, проверяем, является ли он каналом
		if (stat(config.getPathToPlayer1STDIN().c_str(), (struct stat*)&buf)){
			perror("stat");
			exit(0);
		}
		if (!S_ISFIFO(buf.st_mode)){
			cerr << "pipe stdin of player1 not found\n";
			exit(PIPE_NO_FOUND);
		}
	};
	if (!access(config.getPathToPlayer1STDOUT().c_str(), F_OK)){
		//Если файл существует, проверяем, является ли он каналом
		stat(config.getPathToPlayer1STDOUT().c_str(), &buf);
		if (!S_ISFIFO(buf.st_mode)){
			cerr << "pipe stdout of player1 not found\n";
			exit(PIPE_NO_FOUND);
		}
	}else{
		cerr << "pipe stdout of player1 not found\n";
		exit(PIPE_NO_FOUND);		
	}
	if (!access(config.getPathToPlayer2STDIN().c_str(), F_OK)){
		//Если файл существует, проверяем, является ли он каналом
		stat(config.getPathToPlayer2STDIN().c_str(), &buf);
		if (!S_ISFIFO(buf.st_mode)){
			cerr << "pipe stdin of player2 not found\n";
			exit(PIPE_NO_FOUND);
		}
	}else{
			cerr << "pipe stdin of player2 not found\n";
			exit(PIPE_NO_FOUND);		
	}
	if (!access(config.getPathToPlayer2STDOUT().c_str(), F_OK)){
		//Если файл существует, проверяем, является ли он каналом
		stat(config.getPathToPlayer2STDOUT().c_str(), &buf);
		if (!S_ISFIFO(buf.st_mode)){
			cerr << "pipe stdout of player2 not found\n";
			exit(PIPE_NO_FOUND);
		}
	}else{
		cerr << "pipe stdout of player2 not found\n";
		exit(PIPE_NO_FOUND);		
	};
	return ;
};
void timer_print(struct itimerval arg){
	cerr << "sec:" << arg.it_value.tv_sec << "\tms:" << arg.it_value.tv_usec << endl;
}




//Функция проверяет, появилось ли в течении секунды
//что-нибудь для считывания в f
//Если появилось, то true, иначе false
bool checkTimeLimit(ifstream& f){

	//Два таймера, один запускается, во второй считывается 
	//сколько натикало.
	struct itimerval start;
	struct itimerval end;
	//устанавливаем тамер на 100 секунд
	VAL_SEC(start) = 100;
	VAL_MSEC(start) = 0;
	INT_SEC(start) = 100;
	INT_MSEC(start) = 0;
	//запускаем первый таймер
	if (setitimer(ITIMER_REAL, (const struct itimerval *) &start, NULL)){
		perror("setitimer");
		exit(1);
	};
	//считывание с потока
	int c  = f.peek();
	//смотрим сколько прошло времени
	if (getitimer(ITIMER_REAL, (struct itimerval *) &end)){
		perror("getitimer");
		exit(1);				
	};
	//если больше чем секунда, возвращам false
	if ( (VAL_SEC(start) - VAL_SEC(end) - 1) > 0){
		return false;
	}else{
		return true;
	}
}


void closeStream(){
	player1_stdin.close();
	player1_stdout.close();
	player2_stdin.close();
	player2_stdout.close();
	game_log.close();
	return ;
}

void printWins(const u_int& code){
	#ifdef LOG
	if(getFlag(code) == FIRST_WINS){
		game_log << "Player1 WIN. Score:\t"
				 << getNumberScore(code) << endl;
	}else if (getFlag(code) == SECOND_WINS){
		game_log << "Player2 WIN. Score:\t"
				 << getNumberScore(code) << endl;
	}else if(getFlag(code) == DRAW){
			game_log << "Draw. Score:\t"
			 << getNumberScore(code) << endl;
	}
	#endif						
	#ifdef DEBUG
	if(getFlag(code) == FIRST_WINS){
		cerr << "Player1 WIN. Score:\t"
				 << getNumberScore(code) << endl;
	}else if (getFlag(code) == SECOND_WINS){
		cerr << "Player2 WIN. Score:\t"
				 << getNumberScore(code) << endl;
	}else if (getFlag(code) == DRAW){
		game_log << "Draw. Score:\t"
				 << getNumberScore(code) << endl;		
	}
	#endif
	game_result << code << endl;
	game_result.close();
	return ;
}

int main(){
	Config config(PATH_CONFIG);
	BugMap bug_map;
	srand(time(NULL));
	if (access(config.getPathToMap().c_str(), F_OK)){
		#ifdef DEBUG
		cerr << "Error: error in loading or save map.\n";
		#endif
		return MAP_LOAD_SAVE_ERROR;
	}
	if (!bug_map.loadMap(config.getPathToMap())){
		return MAP_FORMAT_ERROR;
	};
	checkPipes(config);
	try
	{
	player1_stdin.open(config.getPathToPlayer1STDIN().c_str(), ofstream::out);
	player1_stdout.open(config.getPathToPlayer1STDOUT().c_str(), ifstream::in);
	player2_stdin.open(config.getPathToPlayer2STDIN().c_str(), ofstream::out);
	player2_stdout.open(config.getPathToPlayer2STDOUT().c_str(), ifstream::in);		
	}
	catch (const exception& exc)
	{
		#ifdef DEBUG
		cerr << exc.what();
		#endif
		return PIPE_NO_FOUND;
		
	}
	int number_step = 0;	// количество ходов
	bool stepOfFirstPlayer = true;
	// Эмуляция игры.
	while(number_step < MAX_NUMBER_STEP){
		#ifdef DEBUG
		cerr << "number Step:\t" << number_step << endl;
		#endif
		if (stepOfFirstPlayer){
			#ifdef LOG
			game_log << "step of first player\nbefore\n";
			game_log << bug_map;
			#endif
			//Выводим карту игроку
			player1_stdin << bug_map;
			//ждём одну секунду
			int k;
			if (checkTimeLimit(player1_stdout)){
				player1_stdout >> k;
				vector<Decree> decrees;
				decrees.clear();
				for (int i = 0; i < k; i++){
						Decree decree;
						player1_stdout >> decree;
						#ifdef DEBUG
						cerr << decree << endl;
						#endif
						decrees.push_back(decree);
				}
				if ( (EXIT_CODE = bug_map.update(decrees, (u_int)1)) != 0){
					//кто-то выиграл
					printWins(EXIT_CODE);
					exit(EXIT_CODE);
				};
				#ifdef LOG
				game_log << "after\n";
				game_log << bug_map;
				game_log << "step of first player end\n";
				#endif
			}else{
				//первый игрок не успел за одну секунду
				#ifdef DEBUG
				cerr << "Player1 TIME LIMIT\n";
				#endif
				EXIT_CODE = TIME_LIMIT;
				setNumberScore(EXIT_CODE, 1);
				exit(EXIT_CODE);
			}
		}else{
			#ifdef LOG
			game_log << "step of second player\nbefore\n";
			game_log << bug_map;
			#endif
			//Выводим карту игроку
			player2_stdin << bug_map;
			//ждём одну секунду
			int k;
			if (checkTimeLimit(player2_stdout)){
				player2_stdout >> k;
				vector<Decree> decrees;
				decrees.clear();
				for (int i = 0; i < k; i++){
					Decree decree;
					player2_stdout >> decree;
					#ifdef DEBUG
					cerr << decree << endl;
					#endif
					decrees.push_back(decree);
				}
				if ( (EXIT_CODE = bug_map.update(decrees, (u_int)2)) != 0){
					//кто-то выиграл
					printWins(EXIT_CODE);
					exit(EXIT_CODE);
				};
				#ifdef LOG
				game_log << "after\n";
				game_log << bug_map;
				game_log << "step of second player end\n";
				#endif
			}else{
				//второй игрок не успел за одну секунду 
				#ifdef DEBUG
				cerr << "Player 2 TIME LIMIT\n";
				#endif
				EXIT_CODE = TIME_LIMIT;
				setNumberScore(EXIT_CODE, 2);
				exit(EXIT_CODE);
			}

		}
		number_step++;
		stepOfFirstPlayer = !stepOfFirstPlayer;
	}
	u_int s1 = bug_map.getScorePlayer1();
	u_int s2 = bug_map.getScorePlayer2();
	if (s1 > s2){
		//выиграл первый игрок
		EXIT_CODE = FIRST_WINS;
		setNumberScore(EXIT_CODE, s1);
		#ifdef LOG
		game_log << "over 1000 steps.\n";
		#endif
		#ifdef DEBUG
		cerr << "over 1000 steps.\n";
		#endif
		printWins(EXIT_CODE);
		closeStream();
		exit(EXIT_CODE);
	}else if(s2 > s1){
		//выиграл второй игрок
		EXIT_CODE = SECOND_WINS;
		setNumberScore(EXIT_CODE, s2);
		#ifdef LOG
		game_log << "over 1000 steps.\n";
		#endif
		#ifdef DEBUG
		cerr << "over 1000 steps.\n";
		#endif
		printWins(EXIT_CODE);
		closeStream();
		exit(EXIT_CODE);
	}else{
		//Победила дружба ;-)
		EXIT_CODE = DRAW;
		setNumberScore(EXIT_CODE, s1);
		#ifdef LOG
		game_log << "over 1000 steps.\n";
		#endif
		#ifdef DEBUG
		cerr << "over 1000 steps.\n";
		#endif		
		printWins(EXIT_CODE);
		closeStream();
		exit(EXIT_CODE);
		
	}
	return 0;
	
	
}
